import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'package:klondike/components/operation.dart';
import 'package:klondike/klondike/components/foundation_pile.dart';
import 'package:klondike/klondike/components/stock_pile.dart';
import 'package:klondike/klondike/components/tableau_pile.dart';
import 'package:klondike/klondike/klondike_game.dart';

import '../util/effect_utils.dart';
import '../enums/rank.dart';
import '../enums/suit.dart';
import '../klondike/klondike_world.dart';
import 'pile.dart';

class Card extends ShapeComponent 
  with DragCallbacks, TapCallbacks, HasWorldReference<KlondikeWorld> {

  Card(int intRank, int intSuit) :
    rank = Rank.fromInt(intRank),
    suit = Suit.fromInt(intSuit),
    frontSprite = Sprite(
      Flame.images.fromCache('${intRank}_of_${Suit.fromInt(intSuit).label}.png'),
    ),
    backSprite = Sprite(Flame.images.fromCache('xiaoxin_background.png')),
    super(size: KlondikeGame.cardSize);
  
  final Rank rank;
  final Suit suit;
  final Sprite frontSprite;
  final Sprite backSprite;

  Pile? pile;

  bool _faceUp = false;

  bool get isFaceUp => _faceUp;
  bool get isFaceDown => !_faceUp;
  void flip() => _faceUp = !_faceUp;

  @override
  String toString() => suit.label + rank.label;

  @override
  void render(Canvas canvas) {
    
    if(_faceUp) {
      _renderFront(canvas);
    } else {
      _renderBack(canvas);
    }
    super.render(canvas);
  }

  void _renderFront(Canvas canvas) {
    frontSprite.render(
      canvas,
      position: size / 2,
      anchor: Anchor.center,
      size: KlondikeGame.cardSize,
    );
  }

  static final Paint backBackgroundPaint = Paint()
    ..color = const Color(0xff380c02);
  static final Paint backBorderPaint1 = Paint()
    ..color = const Color.fromARGB(255, 66, 65, 61)
    ..style = PaintingStyle.stroke
    ..strokeWidth = 10;
  static final Paint backBorderPaint2 = Paint()
    ..color = const Color.fromARGB(92, 105, 101, 95)
    ..style = PaintingStyle.stroke
    ..strokeWidth = 35;
  static final RRect cardRRect = RRect.fromRectAndRadius(
    KlondikeGame.cardSize.toRect(),
    const Radius.circular(KlondikeGame.cardRadius),
  );
  static final RRect backRRectInner = cardRRect.deflate(40);

  void _renderBack(Canvas canvas) {
    canvas.drawRRect(cardRRect, backBackgroundPaint);
    canvas.drawRRect(cardRRect, backBorderPaint1);
    canvas.drawRRect(backRRectInner, backBorderPaint2);
    backSprite.render(
      canvas,
      position: size / 2,
      anchor: Anchor.center,
      size: KlondikeGame.cardSize
    );
  }

  // drag
  bool _isDragging = false;
  final List<Vector2> _whereCardStarted = [];

  List<Card> cards = [];

  @override
  void onDragStart(DragStartEvent event) {
    super.onDragStart(event);
    if(pile is StockPile) {
      _isDragging = false;
      return;
    }

    cards.clear();
    cards.add(this);
    if(pile is TableauPile) {
      final extraCards = (pile! as TableauPile).cardsOnTop(this);
      cards.addAll(extraCards);
    }

    _whereCardStarted.clear();
    for(Card card in cards) {
      _whereCardStarted.add(card.position.clone());
    }

    if(pile?.canMoveCard(cards) ?? false) {
      _isDragging = true;
      for(int i=0; i<cards.length; i++) {
        cards[i].priority = 100 + i;
      }
    }
  }

  @override
  void onDragUpdate(DragUpdateEvent event) {
    if(!_isDragging) {
      return;
    }
    for(Card card in cards) {
      card.position += event.localDelta;
    }
  }

  @override Future<void> onDragEnd(DragEndEvent event) async {
    super.onDragEnd(event);
    if(!_isDragging) {
      return;
    }
    _isDragging = false;

    final dropPiles = parent!
     .componentsAtPoint(position + size / 2)
     .whereType<Pile>()
     .toList();

    if(dropPiles.isNotEmpty) {
      if(dropPiles.first.canAcceptCard(cards)) {
        Pile sourcePile = pile!;
        Pile destPile = dropPiles.first;
        List<Vector2> destPositions = [];

        if (destPile is TableauPile) {
          destPositions = destPile.fetchPositions(cards);
        } else if (destPile is FoundationPile) {
          destPositions.add(destPile.position);
        }

        Operation operation = 
          Operation(List.from(cards), sourcePile, destPile)
            ..hasRevokeMove=true
            ..fromPositions=_whereCardStarted
            ..speed=20.0;

        await operation.proceed();
        if(sourcePile is TableauPile) {
          sourcePile.fillFlip(operation);
        }
        world.histories.add(operation);
        cards.clear();
        return;
      }
    }

    // Invalid drop (middle of nowhere, invalid pile or invalid card for pile).
    await EffectUtils.doMove(
      cards,
      _whereCardStarted,
    );
    pile!.returnCard(cards);
    cards.clear();
  }

  @override
  void onTapUp(TapUpEvent event) {
    handleTapUp();
  }

  Future<void> handleTapUp() async {
    final hitPiles = parent!
     .componentsAtPoint(position + size / 2)
     .whereType<Pile>()
     .toList();

    if(hitPiles.length == 1) {
      hitPiles.first.handleTapUp(this);
    }
  }
}